//////////////////////////////////////////////////////////////////
// MattiseOS Kernel
// Date: 2008
// Author: Matthew Iselin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////

// Includes
#include <mattise.h>
#include <stdint.h>
#include <string.h>
#include <vfs/vfs.h>
#include <exec/elf.h>
#include <mem/util.h>
#include <mem/heap.h>
#include <mem/page.h>
#include <kernio/io.h>
#include <core/portio.h>
#include <process/common.h>
#include <core/intrcommon.h>

// Prototypes

// executes a process, replacing the parent address space with the new process
// one (ie, go through, unmap all non-kernel pages, load new)
int execve( char* name, char** argv, char** env );

// Defines

// system call numbers (put in EAX when system call made)
#define SYSCALL_EXIT		0
#define SYSCALL_KILL		1
#define SYSCALL_SBRK		2
#define SYSCALL_GETPID		3
#define SYSCALL_FORK		4

#define SYSCALL_OPEN		5
#define SYSCALL_READ		6
#define SYSCALL_WRITE		7
#define SYSCALL_SEEK		8
#define SYSCALL_STAT		9
#define SYSCALL_IOCTL		10
#define SYSCALL_CLOSE		11

#define SYSCALL_TIMES		12
#define SYSCALL_EXECVE		13

#define SYSCALL_GETENVIRON	14

#define SYSCALL_GTMOFDAY	15

#define SYSCALL_WAITFORPROC	16

#define SYSCALL_LOADDRIVER	17

#define SYSCALL_SETCWD		18
#define SYSCALL_GETCWD		19

#define SYSCALL_MEMFREE		20

#define SYSCALL_DELAY		21

// new IO functions to provide POSIX compliancy
#define SYSCALL_DUP			22
#define SYSCALL_DUP2		23

// Variables

// whether or not the thread cleanup thread can run
extern int can_clean_threads;

// Functions

// adds a new heap region to the current process memory map [to make sbrk cleaner]
// note that this means the memory map may have a LOT of heap regions in it, but
// that's ok.
void add_heap_region(uint32_t base, uint32_t pg_count)
{
	if(currprocess->memmap == 0)
	{
		currprocess->memmap = (struct ctx_mem_map*) malloc(sizeof(struct ctx_mem_map));
		currprocess->memmap->next = 0;
	}
	else
	{
		struct ctx_mem_map* new_region = (struct ctx_mem_map*) malloc(sizeof(struct ctx_mem_map));
		new_region->next = currprocess->memmap;
		currprocess->memmap = new_region;
	}
	currprocess->memmap->base = base;
	currprocess->memmap->type = MEMMAP_ELEM_HEAP;
	currprocess->memmap->size = pg_count * 0x1000;
}

// the sbrk system call
int sbrk( int incr, struct context* ctx )
{
	// set up the task
	if( ctx->heap_curr == 0 )
	{
		// set the current location
		ctx->heap_curr = ctx->heap_base + incr;

		// find out how many pages to allocate
		uint32_t pagecount = (incr / 0x1000) + 1;

		// allocate space for it
		kAllocateAndMap( pagecount, ctx->heap_base, PF_PRESENT | PF_READWRITE | PF_USER );

		// put into the memory map
		add_heap_region(ctx->heap_base, pagecount);

		// return it
		return ctx->heap_base;
	}

	// save the old heap location
	uint32_t oldheap = ctx->heap_curr;

	// increment the current heap location
	ctx->heap_curr += incr;

	// detect a page skip (if none we don't need to alloacte any more pages)
	uint32_t page1 = oldheap & ~0xFFF;
	uint32_t page2 = ctx->heap_curr & ~0xFFF;
	if( page1 != page2 )
	{
		// how many pages?
		uint32_t pagecount = (page2-page1) / 0x1000;

		// skip FORWARD or else we'll remap, which is bad
		page1 += 0x1000;

		// allocate space for the new page
		kAllocateAndMap( pagecount, page1, PF_PRESENT | PF_READWRITE | PF_USER );

		// create a new region in the memory map for this new space
		add_heap_region(page1, pagecount);
	}

	// return the old top of the heap
	return oldheap;
}

// the system call handler (for interrupts - syscall/sysenter go elsewhere, when implemented)
void SysCallInt80( struct stack_layout* r )
{
	//dprintf("[%s] call=%d\n", __PRETTY_FUNCTION__, r->eax);

	// figure out what to do...
	switch( r->eax )
	{
		// exit system call
		case SYSCALL_EXIT:

			// kill the current process
			kill( getpid() );

			// this'll give up the timeslice in case kill falls through
			while(1);

			break;

		// kill system call
		case SYSCALL_KILL:

			// TODO: when signal handling is implemented, handle signals here

			// kills a process
			kill( r->ebx );
			r->edi = 0;

			break;

		// extends the process heap
		case SYSCALL_SBRK:

			// return the new location
			r->edi = sbrk( r->ebx, currprocess );

			break;

		// gets the current process id
		case SYSCALL_GETPID:

			// return it
			r->edi = getpid();

			break;

		// forks the process
		case SYSCALL_FORK:

			// return whatever fork returns
			r->edi = fork();

			break;

		// opens a file
		case SYSCALL_OPEN:

			// open it
			r->edi = sys_open( (const char*) r->esi, r->ebx );

			break;

		// reads data from a file
		case SYSCALL_READ:

			// read the file
			//dprintf("sys_read( %d, %x, %d );\n", r->ebx, r->esi, r->ecx);
			r->edi = sys_read( r->ebx, (void*) r->esi, r->ecx );

			break;

		// writes data to a file
		case SYSCALL_WRITE:

			// write to the file
			r->edi = sys_write( r->ebx, (char*) r->esi, r->ecx );

			break;

		// seeks within a file
		case SYSCALL_SEEK:

			// seek within the file
			r->edi = sys_seek( r->ebx, r->ecx, r->edx );

			break;

		// gets data about a file
		case SYSCALL_STAT:

			// get the information
			r->edi = sys_stat( r->ebx, (struct stat*) r->esi );

			break;

		// controls an i/o stream
		case SYSCALL_IOCTL:

			// run it
			r->edi = sys_ioctl( r->ebx, r->ecx, r->edx );

			break;

		// closes a file
		case SYSCALL_CLOSE:

			// close the file
			r->edi = sys_close( r->ebx );

			break;

		// gets timing information about the current process
		case SYSCALL_TIMES:

			break;

		// executes a program (with environment)
		case SYSCALL_EXECVE:

			// call the function
			r->edi = execve( (char*) r->esi, (char**) r->ecx, (char**) r->edx );

			break;

		// retrieves the environment information for a process
		case SYSCALL_GETENVIRON:

			break;

		// retrieves the current time
		case SYSCALL_GTMOFDAY:
			{

			struct timeval {
				long tv_sec;
				unsigned long tv_usec;
			} *tm = (struct timeval*) r->esi;

			struct timezone {
				int tz_minuteswest;
				int tz_dsttime;
			} *tv = (struct timezone*) r->ebx;

			// real time clock ports
			#define RTC_BASE 0x70
			#define RTC_DATA 0x71

			// wait until we can read the data
			int waitc = 1;
			while( waitc )
			{
				// update in progress?
				outb( RTC_BASE, 0xA );

				// get the data
				unsigned char c = inb( RTC_DATA );

				// check
				if( ! ( c & 0x80 ) )
				{
					// done
					waitc = 0;
				}
			}

			// seconds
			outb( RTC_BASE, 0 );
			unsigned char sec = inb( RTC_DATA );

			// minutes
			outb( RTC_BASE, 2 );
			unsigned char min = inb( RTC_DATA );

			// hours
			outb( RTC_BASE, 4 );
			unsigned char hour = inb( RTC_DATA );

			// day
			outb( RTC_BASE, 7 );
			unsigned char day = inb( RTC_DATA );

			// month
			outb( RTC_BASE, 8 );
			unsigned char month = inb( RTC_DATA );

			// year (only 2 digits, between 2000-2099)
			outb( RTC_BASE, 9 );
			unsigned char year = inb( RTC_DATA );

			// how do we deal with the data?
			outb( RTC_BASE, 0xB );
			unsigned char format = inb( RTC_DATA );

			// convert all fields from BDC to binary if bit is clear
			if( ! ( format & 0x4 ) )
			{
				// convert
				sec = ( sec & 0xF ) + ( sec >> 4 ) * 10;
				min = ( min & 0xF ) + ( min >> 4 ) * 10;
				hour = ( hour & 0xF ) + ( ( hour & 0x70 ) >> 4 ) * 10 + ( hour & 0x80 );
				day = ( day & 0xF ) + ( day >> 4 ) * 10;
				month = ( month & 0xF ) + ( month >> 4 ) * 10;
				year = ( year & 0xF ) + ( year >> 4 ) * 10;
			}

			// convert hours --> 24 format
			if( ! ( format & 0x2 ) )
			{
				// am/pm?
				int ampm = hour & 0x80;

				// and the bits
				hour &= 0x7F;

				// fix 24:00 = 12am oddity
				if( hour == 12 ) hour = 0;
				if( ampm ) hour += 12;
			}

			// don't take this into the past before 2000, it won't work
			// also don't take into future, past 2100 (by which time I'll be dead), same reason

			// days since 2000
			unsigned int fullyearsdays = year * 365 + ( ( year - 1 ) / 4 ) + 1;

			// offset of days for each month
			static unsigned int dayoffset[12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };

			// adjust day for leap year
			unsigned int thisyeardays = dayoffset[month-1] + day - ((( year % 4 ) == 0 && month > 2 ) ? 0 : 1 ); // only if no leap day passed

			// sum days before and during this year to get date, adjust for unix epoch
			unsigned int date = fullyearsdays + thisyeardays + 10957; // 10957 days from 1970 to 2000

			// get the time now - seconds past the epoch
			long time = sec + 60 * (min + 60 * ( hour + 24 * date ) );

			// ok, now we set the structure contents
			if( tm )
			{
				tm->tv_sec = time;
				tm->tv_usec = (unsigned long) time;
			}

			// set the timezone
			if( tv )
			{
				tv->tz_minuteswest = 0;
				tv->tz_dsttime = 0;
			}

			// no error code is ever returned
			r->edi = 0;

			}
			break;

		// we're waiting for a process to finish running
		case SYSCALL_WAITFORPROC:
			{

			// get the PID to search for
			struct pentry* ent = 0;
			uint32_t pid = r->ebx;
			if((int32_t) pid == -1)
				return;

			// now find it in a queue - it'll be in either the ready or already queues
			asm volatile("cli");
			for(ent = p_readyq; ent != 0; ent = ent->next)
				if(ent->me->id == pid)
					break;
			if(ent == 0)
			{
				for(ent = p_alreadyq; ent != 0; ent = ent->next)
					if(ent->me->id == pid)
						break;
			}
			asm volatile("sti");

			// no entry?
			if(ent == 0)
				return;

			// ask to be woken when the process dies
			ent->me->wake_on_die = currprocess;
			wait();

			}
			break;

		case SYSCALL_LOADDRIVER:
			{
				// this loads a driver for use, from any device
				printf( "opening?\n" );
				int fd = vfs_open( (char*) r->esi, 0 );
				printf( "loading driver: %s - %d\n", (char*) r->esi, fd );
				struct stat s;
				vfs_stat( fd, &s );
				char* ptr = (char*) malloc( s.st_size + 1 );
				vfs_read( fd, ptr, s.st_size );
				printf( "loading elf...\n" );
				r->edi = LoadElf( GetKernelDirectory(), (uint32_t) ptr, 1, 0, 0, (char*) r->esi, 0 );
				printf( "done?\n" );
				free( ptr );
				vfs_close( fd );
			}
			break;

		case SYSCALL_SETCWD:

			// set the current working directory
			r->edi = 0;
			if( r->esi != 0 )
				strcpy( currprocess->currdir, (char*) r->esi );
			else
				r->edi = -1;

			break;

		case SYSCALL_GETCWD:

			// get the current working directory
			r->edi = 0;
			size_t len = strlen(currprocess->currdir);
			if( r->esi != 0 )
				memcpy( (void*) r->esi, currprocess->currdir, r->ebx > len ? r->ebx : len );
			else
				r->edi = -1;

			break;

		case SYSCALL_MEMFREE:
			{

			// return the number of bytes free
			r->edi = kGetAvailMem();

			}
			break;

		case SYSCALL_DELAY:

			delay(r->ebx);

			break;

		case SYSCALL_DUP:

			r->edi = sys_dup( r->ebx );

			break;

		case SYSCALL_DUP2:

			dprintf("[SYSC ] dup2 is not implemented\n");
			r->edi = -1;

			break;

		// debugging only
		default:
			dprintf( "Unhandled syscall: %d\n", r->eax );
	}
}

// installs system call handling
void kInstallSystemCalls()
{
	// install it on interrupt 0x80
	kInstallCustomIntrHandler( 0x80, SysCallInt80 );
}
